#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sobjc-commons/defines.h>
#include <sobjc-commons/utils.h>
#include <sobjc-commons/st.h>
#include "class.h"
#include "command.h"
#include "context.h"
#include "name.h"
#include "parse.h"
#include "scope.h"

struct class_iter {
  int n;
  objc_class_t *classes;
};

static objc_type_encoding_t make_type_encoding_from_method_types(objc_node_t, int, objc_node_t *);
static objc_type_list_t scan_node(objc_node_t, objc_type_t);
static int distribute_dispatch_codes(objc_class_t, BOOL);

static int iter_class_callback(char *, objc_class_t, struct class_iter *);
static st_table_t class_table = NULL;
static st_table_t sig_table = NULL;
static int nclasses = 0;

objc_class_t objc_root_class = NULL;

void
objc_register_class(objc_class_t cls)
{
  if (class_table == NULL)
    class_table = st_init_strtable();
  cls->class_id = nclasses++;
  st_insert(class_table, cls->name, cls);

  if (STREQ(cls->name, "Object"))
    objc_root_class = cls;
}

objc_class_t
objc_get_class(char *name)
{
  objc_class_t cls = NULL;

  if (class_table != NULL)
    st_lookup(class_table, name, (st_data_t *)&cls);

  return cls;
}

void
objc_iter_classes(objc_class_iter_f f, void *buf)
{
  struct class_iter it;
  int i;

  it.n = 0;
  it.classes = ALLOC_N(objc_class_t, nclasses);
  st_foreach(class_table, iter_class_callback, &it);

  for (i = 0; i < nclasses; i++)
    f(it.classes[i], buf);

  objc_free(it.classes);
}

static int
iter_class_callback(char *name __attribute((unused)),
                    objc_class_t cls, struct class_iter *it)
{
  it->n++;
  it->classes[cls->class_id] = cls;

  if (it->n <= nclasses)
    return ST_CONTINUE;
  else
    return ST_STOP;
}

objc_class_t
objc_make_class(const char *name, objc_class_t superclass)
{
  objc_class_t cls;

  cls = ALLOC(struct objc_class);
  cls->class_id = -1;
  cls->name = objc_strdup(name);
  cls->getter = objc_make_get_class_object_func_name(name);
  cls->has_impl = NO;
  cls->superclass = superclass;
  cls->ivars = NULL;
  cls->ivars_repr = NULL;
  cls->inst_meths = NULL;
  cls->class_meths = NULL;
  cls->protocols = NULL;
  cls->categories = NULL;
  cls->is_abstract = NO;
  cls->is_sealed = NO;
  cls->inst_dcodes = NULL;
  cls->class_dcodes = NULL;

  return cls;
}

objc_ivar_t
objc_class_get_ivar(objc_class_t cls, const char *name)
{
  objc_ivar_list_t ivars;

  ivars = cls->ivars;
  while (ivars) {
    if (STREQ(ivars->car->name, name))
        return ivars->car;
    ivars = ivars->cdr;
  }

  if (cls->superclass != NULL)
    return objc_class_get_ivar(cls->superclass, name);
  else
    return NULL;
}

objc_method_t
objc_class_get_method(objc_class_t cls, const char *name, BOOL is_inst)
{
  objc_method_list_t meths;

  if (is_inst)
    meths = cls->inst_meths;
  else
    meths = cls->class_meths;

  while (meths) {
    if (STREQ(meths->car->sig->name, name))
      return meths->car;
    meths = meths->cdr;
  }

  if (cls->superclass != NULL)
    return objc_class_get_method(cls->superclass, name, is_inst);
  else
    return NULL;
}

BOOL
objc_class_is_kind_of(objc_class_t cls, objc_class_t base)
{
  while (cls) {
    if (cls == base)
      return YES;
    cls = cls->superclass;
  }
  return NO;
}

objc_method_sig_t
objc_register_method_sig(char *name, objc_node_t mth_dclr)
{
  objc_method_sig_t sig;
  objc_node_t mth_sel, mtype, *ptypes = NULL;

  if (sig_table == NULL)
    sig_table = st_init_strtable();

  sig = ALLOC(struct objc_method_sig);
  sig->name = name;

  mtype = mth_dclr->a.mth_dclr.mth_spcr->a.mth_spcr.mtype;
  mth_sel = mth_dclr->a.mth_dclr.mth_sel;
  switch (mth_sel->type) {
  case OBJC_MTH_UNARY_SEL_NODE:
    sig->nargs = 0;
    break;
  case OBJC_MTH_KW_SEL_NODE:
    sig->nargs = mth_sel->a.mth_kw_sel.sel->a.l.nsubs;
    sig->vargs = mth_sel->a.mth_kw_sel.vargs;
    ptypes = mth_sel->a.mth_kw_sel.sel->a.l.subs;
    break;
  }

  sig->mtype = mtype;
  sig->enc = make_type_encoding_from_method_types(mtype, sig->nargs, ptypes);
  st_insert(sig_table, sig->name, sig);

  return sig;
}

objc_method_sig_t
objc_get_method_sig(char *name)
{
  objc_method_sig_t sig = NULL;

  if (sig_table != NULL)
    st_lookup(sig_table, name, (st_data_t *)&sig);

  return sig;
}

static objc_type_encoding_t
make_type_encoding_from_method_types(objc_node_t mnode,
                                     int nargs,
                                     objc_node_t *pnodes)
{
  objc_type_t type;
  objc_type_list_t types;
  int i;

  /* method return */
  type = objc_make_type();
  scan_node(mnode, type);
  types = objc_make_type_list(type, NULL);

  /* receiver and selector */
  type = objc_make_type();
  type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
  type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;
  objc_type_list_add_set(&types, type);
  type = objc_make_type();
  type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
  type->body.prim = OBJC_TYPE_SEL_PRIM_TYPE;
  objc_type_list_add_set(&types, type);

  /* parameters */
  for (i = 0; i < nargs; i++) {
    type = objc_make_type();
    scan_node(pnodes[i], type);
    objc_type_list_add_set(&types, type);
  }
  return objc_make_type_encoding(types);
}

static objc_type_list_t
scan_node(objc_node_t node, objc_type_t type)
{
  int i;
  objc_class_t cls;
  objc_ivar_list_t ivars;
  objc_type_t subtype;
  objc_type_list_t subtypes = NULL;

  if (node == NULL)
    return NULL;

  /* printf(AT_scan_node "scan %s\n", objc_node_name(node)); */
  switch (node->type) {
  case OBJC_SPCR_QUAL_LIST_NODE:
  case OBJC_STRUCT_DCLR_LIST_NODE:
    for (i = 0; i < node->a.l.nsubs; i++)
      scan_node(node->a.l.subs[i], type);
    break;

  case OBJC_IVAR_DECL_NODE:
    /* scan pointer first */
    scan_node(node->a.ivar_decl.dclr, type);
    scan_node(node->a.ivar_decl.spcr_qual_list, type);
    break;

  case OBJC_TYPE_NAME_NODE:
    /* scan pointer first */
    scan_node(node->a.type_name.abs_dclr, type);
    scan_node(node->a.type_name.spcr_qual_list, type);
    break;

  case OBJC_TYPEDEF_NAME_NODE:
    if (STREQ(node->a.sval, OBJC_TYPE_ID_NAME)) {
      type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
      type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;
    } else if (STREQ(node->a.sval, OBJC_TYPE_SEL_NAME)) {
      type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
      type->body.prim = OBJC_TYPE_SEL_PRIM_TYPE;
    } else if (STREQ(node->a.sval, OBJC_TYPE_CLASS_NAME)) {
      type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
      type->body.prim = OBJC_TYPE_CLASS_PRIM_TYPE;
    } else {
      /* IMPLEMENTME */
    }
    break;

  case OBJC_INIT_DCLR_NODE:
    scan_node(node->a.init_dclr.dclr, type);
    break;

  case OBJC_DCLR_NODE:
    scan_node(node->a.dclr.base_dclr, type);
    break;

  case OBJC_BASE_DCLR_NODE:
    /* scan pointer first */
    scan_node(node->a.base_dclr.ptr, type);
    scan_node(node->a.base_dclr.dir_dclr, type);
    break;

  case OBJC_TYPE_QUAL_NODE:
    switch (node->a.word) {
    case CONST: case CONST2: case CONST3:
      type->quals |= OBJC_TYPE_CONST_QUAL;
      break;
    }
    break;

  case OBJC_TYPE_SPCR_NODE:
    type->code_type = OBJC_TYPE_PRIM_CODE_TYPE; 
    switch (node->a.word) {
    case CHAR:
      if (type->nptrs > 0)
        type->body.prim = OBJC_TYPE_STR_PRIM_TYPE;
      else
        type->body.prim = OBJC_TYPE_CHAR_PRIM_TYPE;
      break;

    case UNSIGNED:
      type->body.prim = OBJC_TYPE_UINT_PRIM_TYPE;
      break;

    case SHORT:
      if (type->body.prim == OBJC_TYPE_UINT_PRIM_TYPE)
        type->body.prim = OBJC_TYPE_USHORT_PRIM_TYPE;
      else
        type->body.prim = OBJC_TYPE_SHORT_PRIM_TYPE;
      break;

    case INT:
      if (type->body.prim == OBJC_TYPE_UINT_PRIM_TYPE)
        type->body.prim = OBJC_TYPE_UINT_PRIM_TYPE;
      else
        type->body.prim = OBJC_TYPE_INT_PRIM_TYPE;
      break;

    case LONG:
      if (type->body.prim == OBJC_TYPE_UINT_PRIM_TYPE)
        type->body.prim = OBJC_TYPE_ULONG_PRIM_TYPE;
      else
        type->body.prim = OBJC_TYPE_LONG_PRIM_TYPE;
      break;

    case FLOAT:     type->body.prim = OBJC_TYPE_FLOAT_PRIM_TYPE;    break;
    case DOUBLE:    type->body.prim = OBJC_TYPE_DOUBLE_PRIM_TYPE;   break;
    case VOID:      type->body.prim = OBJC_TYPE_VOID_PRIM_TYPE;     break;
    }
    break;

  case OBJC_KW_DCLR_NODE:
    scan_node(node->a.kw_dclr.mtype, type);
    break;

  case OBJC_ABS_DCLR_NODE:
    scan_node(node->a.abs_dclr.ptr, type);
    scan_node(node->a.abs_dclr.dir_abs_dclr, type);
    break;

  case OBJC_PTR_NODE:
    type->nptrs++;
    scan_node(node->a.ptr.ptr, type);
    break;

  case OBJC_CLS_NAME_NODE:
    if (type->nptrs > 0) {
      type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
      type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;
      type->nptrs--;
    } else {
      type->code_type = OBJC_TYPE_STRUCT_CODE_TYPE;
      type->body.su.name = node->a.sval;
      cls = objc_get_class(node->a.sval);
      type->body.su.mems = NULL;
      ivars = cls->ivars;
      while (ivars) {
        subtype = objc_make_type();
        scan_node(ivars->car->node, subtype);
        objc_type_list_add_set(&type->body.su.mems, subtype);
        ivars = ivars->cdr;
      }
    }
    break;

  case OBJC_IDENT_NODE:
    type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
    type->body.prim = OBJC_TYPE_UNKNOWN_PRIM_TYPE;
    break;

  case OBJC_SU_SPCR_NODE:
    if (node->a.su_spcr.type)
      type->code_type = OBJC_TYPE_STRUCT_CODE_TYPE;
    else
      type->code_type = OBJC_TYPE_UNION_CODE_TYPE;
    type->body.su.name = node->a.su_spcr.name;
    type->body.su.mems = NULL;

    if (type->nptrs <= 1) {
      if (type->body.su.name != NULL) {
        type->body.su.mems = scan_node(objc_scope_get_struct(objc_root_scope, type->body.su.name)->
                             a.type.spcr->a.su_spcr.decl_list, NULL);
      } else
        type->body.su.mems = scan_node(node->a.su_spcr.decl_list, NULL);
    }
    break;

  case OBJC_STRUCT_DECL_LIST_NODE:
    for (i = 0; i < node->a.l.nsubs; i++) {
      subtype = objc_make_type();
      scan_node(node->a.l.subs[i], subtype);
      objc_type_list_add_set(&subtypes, subtype);
    }
    break;

  case OBJC_STRUCT_DECL_NODE:
    scan_node(node->a.struct_decl.dclr_list, type);
    scan_node(node->a.struct_decl.spcr_qual_list, type);
    break;

  case OBJC_STRUCT_DCLR_NODE:
    /* bitfield */
    /*
    if (node->a.struct_dclr.expr)
      type->body.nbits = node->a.struct_dclr.expr;
    else
      scan_node(node->a.struct_dclr.dclr, type);
    */
    if (!node->a.struct_dclr.expr)
      scan_node(node->a.struct_dclr.dclr, type);
    break;

  default:
    objc_print_error("cannot convert node %s to type encoding",
                     objc_node_name(node));
  }

  return subtypes;
}

objc_type_encoding_t
objc_make_type_encoding_from_node(objc_node_t node)
{
  objc_type_t type;

  type = objc_make_type();
  scan_node(node, type);
  return objc_make_type_encoding(objc_make_type_list(type, NULL));
}

int
objc_get_dispatch_code(objc_class_t cls, const char *meth_name, BOOL is_inst)
{
  int code;
  st_table_t dcodes;

  if (is_inst)
    dcodes = cls->inst_dcodes;
  else
    dcodes = cls->class_dcodes;

  if (dcodes == NULL) {
    distribute_dispatch_codes(cls, is_inst);
    if (is_inst)
      dcodes = cls->inst_dcodes;
    else
      dcodes = cls->class_dcodes;
  }

  if (st_lookup(dcodes, meth_name, (st_data_t *)&code))
    return code;
  else if (cls->superclass)
    return objc_get_dispatch_code(cls->superclass, meth_name, is_inst);
  else
    return -1;
}

static int
distribute_dispatch_codes(objc_class_t cls, BOOL is_inst)
{
  st_table_t dcodes;
  int n;
  objc_method_list_t meths;

  if (cls->superclass) {
    if (is_inst)
      if (cls->superclass->inst_dcodes == NULL)
        n = distribute_dispatch_codes(cls->superclass, is_inst);
      else
        n = cls->superclass->inst_dcodes->num_entries;
    else 
      if (cls->class_dcodes == NULL)
        n = distribute_dispatch_codes(cls->superclass, is_inst);
      else
        n = cls->superclass->class_dcodes->num_entries;
  } else
    n = 0;

  if (is_inst)
    meths = cls->inst_meths;
  else
    meths = cls->class_meths;

  dcodes = st_init_strtable();
  while (meths) {
    st_add_direct(dcodes, meths->car->sig->name, (st_data_t)n++);
    meths = meths->cdr;
  }

  if (is_inst)
    cls->inst_dcodes = dcodes;
  else
    cls->class_dcodes = dcodes;

  return n;
}

